/*******************************************************************************
 * This file is part of blueprint4j www.blueprint4j.org.
 * 
 *     blueprint4j is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     blueprint4j is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with blueprint4j. If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package blueprint4j.gui;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.text.ParseException;

import javax.swing.JButton;
import javax.swing.JOptionPane;

import blueprint4j.db.Entity;
import blueprint4j.db.Field;
import blueprint4j.db.FieldAction;
import blueprint4j.db.FieldGroup;
import blueprint4j.db.FieldProgress;
import blueprint4j.db.VectorField;
import blueprint4j.report.db.FieldReportGroup;
import blueprint4j.report.db.VectorFieldReport;
import blueprint4j.utils.BindBigDecimalInterface;
import blueprint4j.utils.BindBooleanInterface;
import blueprint4j.utils.BindCharacterInterface;
import blueprint4j.utils.BindColorInterface;
import blueprint4j.utils.BindCurrencyInterface;
import blueprint4j.utils.BindDateInterface;
import blueprint4j.utils.BindDateTimeInterface;
import blueprint4j.utils.BindDoubleInterface;
import blueprint4j.utils.BindException;
import blueprint4j.utils.BindFieldInterface;
import blueprint4j.utils.BindFileLocationInterface;
import blueprint4j.utils.BindImageInterface;
import blueprint4j.utils.BindIntegerInterface;
import blueprint4j.utils.BindPasswordInterface;
import blueprint4j.utils.BindSetCombinerInterface;
import blueprint4j.utils.BindStringBoxInterface;
import blueprint4j.utils.BindStringInterface;
import blueprint4j.utils.BindStringSelectInterface;
import blueprint4j.utils.BindStringSelectRadioInterface;
import blueprint4j.utils.BindTimeInterface;
import blueprint4j.utils.Bindable;
import blueprint4j.utils.FieldBinding;
import blueprint4j.utils.Log;
import blueprint4j.utils.VectorBindFieldInterface;
import blueprint4j.utils.VectorBindable;
import blueprint4j.utils.VectorFieldBinding;

public class Binder implements ActionListener,Cloneable {
    protected VectorBinder binders = new VectorBinder();
    protected VectorBindable bindables = new VectorBindable();
    protected VectorBindable deletes = new VectorBindable();
    protected VectorFieldBinding bindings = new VectorFieldBinding();
    protected VectorBinderListner listners=new VectorBinderListner();
    
    public Binder() {}
    
    /**
     * Have any of the field values been changed?
     */
    public boolean isChanged() {
        return binders.isChanged() || bindings.haveAnyChanged();
    }
    
    /**
     * Save all field values and then save the bindables
     */
    public void save()
    throws BindException {
        synchronized (binders) {
            binders.save();
            bindings.intoBindable();
            deletes.deleteAll();
            bindables.save();
            bindables.commit();
            listners.saved(this);
        }
    }
    
    /**
     * Restore all the field values from the bindables
     */
    public void restore() {
        synchronized (binders) {
            binders.restore();
            bindings.intoControl();
            listners.restore(this);
        }
    }
    
    /**
     * Reload these entites from the database
     */
    public void reload()
    throws BindException {
        synchronized (binders) {
            binders.reload();
            bindables.reload();
            deletes.removeAll();
            bindings.intoControl();
            listners.reload(this);
        }
    }
    
    public void addBinderListner(BinderListner listner){
        listners.add(listner);
    }
    
    public void removeBinderListner(BinderListner listner){
        listners.remove(listner);
    }
    
    /**
     * Updates all the bindable fields with the component values
     */
    public void intoBindings()
    throws BindException {
        synchronized (binders) {
            bindings.intoBindable();
            listners.intoBindings(this);
        }
    }
    
    /**
     * Updates all the bindable fields with the component values
     */
    public void intoControls()
    throws BindException {
        synchronized (binders) {
            bindings.intoControl();
        }
    }
    
    public VectorFieldBinding getVectorFieldBind(VectorBindFieldInterface vectorField) {
        return bindings.get(vectorField);
    }
    
    public void addBinder(Binder binder) {
        binders.add(binder);
    }
    
    public void removeBinder(Binder binder) {
        binders.remove(binder);
    }
    
    // Add field
    public void addFieldBinding(FieldBinding binding) {
        if (!bindings.contains(binding)) {
            bindings.add(binding);
            addBindable(binding.getBindable());
        }
    }
    
    private void removeBindableBinding(Bindable bindable) {
        for (int t=bindings.size()-1;t>=0;t--){
            if (bindings.get(t).getBindable()==bindable){
                bindings.remove(t);
            }
        }
    }
    
    // Add an bindable fro delete
    public void addBindableToDelete(Bindable delete) {
        // Remove all this entites fields bindings
        removeBindableBinding(delete);
        // Add for delet
        deletes.add(delete);
        // Remove from save
        bindables.remove(delete);
    }
    
    public void addBindable(Bindable bindable) {
        if (!bindables.contains(bindable)) {
            bindables.add(bindable);
        }
    }
    
    public void removeBindable(Bindable bindable) {
        bindables.remove(bindable);
    }
    
    public JButton createSaveButton() {
        JButton button = new JButton("Save");
        button.addActionListener(this);
        return button;
    }
    
    public void actionPerformed(ActionEvent e) {
        try {
            save();
        } catch (Exception ex) {
            Log.trace.out( ex);
            String message = ex.getMessage();
            if (message == null || message.equals("")) {
                message = ex.getClass().getName();
            }
            JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE);
        }
    }
    
    public DataGroup createDataGroup(VectorDataPair vdata_pair, Bindable bindable,String name)
    throws BindException {
        DataGroup group= new DataGroup(name);
        for (int i = 0;i < bindable.getBindFields().length;i++) {
        	if (!(bindable.getBindFields()[i] instanceof Field) || !((Field)bindable.getBindFields()[i]).is(Field.FIELD_INVISABLE)){
	            DataPair pair = createDataPair(bindable.getBindFields()[i],true);
	            vdata_pair.add(pair);
	            group.add(pair);
        	}
        }
        return group;
    }
    
    public DataPair createDataPairFieldAction(FieldAction action)
    throws BindException{
        try {
            DataPairFieldAction pair = new DataPairFieldAction(this,action);
//            action.setBinder(this);
            return pair;
        } catch (Exception exception) {
            throw new BindException(exception);
        }
    }
    
    public DataPair createDataPairFieldProgress(FieldProgress progress)
    throws BindException{
        try {
            DataPairFieldProgress pair = new DataPairFieldProgress(this,progress);
            return pair;
        } catch (Exception exception) {
            throw new BindException(exception);
        }
    }
    
    public DataPair createDataPair(BindFieldInterface p_field,boolean editable)
    throws BindException {
        if (p_field.isReadOnly()) {
            editable=false;
        }
        if (p_field instanceof FieldProgress) {
            return createDataPairFieldProgress((FieldProgress) p_field);
        }
        if (p_field instanceof BindColorInterface) {
            return createDataPairFieldColor((BindColorInterface) p_field,editable);
        }
        if (p_field instanceof BindDateTimeInterface) {
            return createDataPairFieldDateTime((BindDateTimeInterface) p_field,editable);
        }
        if (p_field instanceof BindCurrencyInterface) {
            return createDataPairFieldCurrency((BindCurrencyInterface) p_field,editable);
        }
        if (p_field instanceof BindDoubleInterface) {
            return createDataPairFieldDouble((BindDoubleInterface) p_field,editable);
        }
        if (p_field instanceof BindBigDecimalInterface) {
            return createDataPairFieldBigDecimal((BindBigDecimalInterface) p_field,editable);
        }
        if (p_field instanceof BindSetCombinerInterface) {
            return createDataPairFieldSetCombiner((BindSetCombinerInterface) p_field,editable);
        }
        if (p_field instanceof BindStringSelectRadioInterface) {
            return createDataPairFieldStringSelectRadio((BindStringSelectRadioInterface) p_field,editable);
        }
        if (p_field instanceof BindStringSelectInterface) {
            return createDataPairFieldStringSelect((BindStringSelectInterface) p_field,editable);
        }
        if (p_field instanceof BindImageInterface) {
            return createDataPairFieldImage((BindImageInterface) p_field,editable);
        }
        if (p_field instanceof BindCharacterInterface) {
            return createDataPairFieldCharacter((BindCharacterInterface) p_field,editable);
        }
        if (p_field instanceof BindFileLocationInterface) {
            return createDataPairFileLocation((BindFileLocationInterface) p_field,true);
        }
        if (p_field instanceof BindPasswordInterface) {
            return createDataPairFieldStringPassword((BindPasswordInterface) p_field);
        }
        if (p_field instanceof BindStringBoxInterface) {
            return createDataPairFieldStringBox((BindStringBoxInterface) p_field,editable);
        }
        if (p_field instanceof BindStringInterface) {
            return createDataPairFieldString((BindStringInterface) p_field,editable);
        }
        if (p_field instanceof BindIntegerInterface) {
            return createDataPairFieldInteger((BindIntegerInterface) p_field,editable);
        }
        if (p_field instanceof FieldAction) {
            return createDataPairFieldAction((FieldAction) p_field);
        }
        if (p_field instanceof BindBooleanInterface) {
            return createDataPairFieldBoolean((BindBooleanInterface) p_field,editable);
        }
        if (p_field instanceof BindDateInterface) {
            return createDataPairFieldDate((BindDateInterface) p_field,editable);
        }
        if (p_field instanceof BindTimeInterface) {
            return createDataPairFieldTime((BindTimeInterface) p_field,editable);
        }
        throw new BindException("Could not find match for ["+p_field+"]");
    }
    
    public DataPair createDataPair(BindFieldInterface p_field)
    throws BindException {
        return createDataPair(p_field,true);
    }
    
    private DataPairFieldString createDataPairFieldString(BindStringInterface p_field, boolean editable) {
        DataPairFieldString pair = new DataPairFieldString(this,p_field, editable);
        addFieldBinding(pair);
        return pair;
    }
    
    private DataPairFieldCharacter createDataPairFieldCharacter(BindCharacterInterface p_field,boolean editable) {
        DataPairFieldCharacter pair = new DataPairFieldCharacter(this,p_field,editable);
        addFieldBinding(pair);
        return pair;
    }
    
    private DataPairFieldStringBox createDataPairFieldStringBox(BindStringBoxInterface p_field, boolean editable) {
        DataPairFieldStringBox pair = new DataPairFieldStringBox(this,p_field, editable);
        addFieldBinding(pair);
        return pair;
    }
    
    public DataPairFieldStringBox createDataPairFieldStringBox(BindStringInterface p_field, boolean editable,int rows,int cols) {
        DataPairFieldStringBox pair = new DataPairFieldStringBox(this,p_field, editable, rows, cols);
        addFieldBinding(pair);
        return pair;
    }
    
    public DataPairFieldString createDataPairFieldString(BindStringInterface p_field, String pattern, String place_holder)
    throws ParseException {
        DataPairFieldString pair = new DataPairFieldString(this,p_field, pattern, place_holder);
        addFieldBinding(pair);
        return pair;
    }
    
   public DataPairFieldStringPassword createDataPairFieldStringPassword(BindPasswordInterface p_field) {
        DataPairFieldStringPassword pair = new DataPairFieldStringPassword(this,p_field);
        addFieldBinding(pair);
        return pair;
    }
    
        /*
        public DataPairFieldStringCombo createDataPairFieldStringCombo(BindStringInterface p_field, String[] values,boolean editable) {
        DataPairFieldStringCombo pair = new DataPairFieldStringCombo(p_field, values,editable);
        addFieldBinding(pair);
        return pair;
}*/
    
    public DataPairFieldStringCombo createDataPairFieldStringCombo(BindStringInterface p_field, String[] display, String[] values,boolean editable) {
        DataPairFieldStringCombo pair = new DataPairFieldStringCombo(this,p_field, display,values,editable);
        addFieldBinding(pair);
        return pair;
    }
    
    private DataPairFieldStringSelect createDataPairFieldStringSelect(BindStringSelectInterface p_field,boolean editable) throws BindException {
        DataPairFieldStringSelect pair = new DataPairFieldStringSelect(this,p_field, p_field.getDisplays(),p_field.getOptions(),editable);
        addFieldBinding(pair);
        return pair;
    }
    
    public DataPairFieldStringRadio createDataPairFieldStringSelectRadio(BindStringSelectRadioInterface p_field, boolean editable) {
        DataPairFieldStringRadio pair = new DataPairFieldStringRadio(this,p_field,editable);
        addFieldBinding(pair);
        return pair;
    }
    
    private DataPairFieldImage createDataPairFieldImage(BindImageInterface p_field,boolean editable) {
        DataPairFieldImage pair = new DataPairFieldImage(this,p_field,editable);
        addFieldBinding(pair);
        return pair;
    }
    
    private DataPairFileLocation createDataPairFileLocation(BindFileLocationInterface p_field,boolean editable) {
        DataPairFileLocation pair = new DataPairFileLocation(this,p_field,editable);
        addFieldBinding(pair);
        return pair;
    }
    
    private DataPairFieldSetCombiner createDataPairFieldSetCombiner(BindSetCombinerInterface p_field,boolean editable) {
        DataPairFieldSetCombiner pair = new DataPairFieldSetCombiner(this,p_field,editable);
        addFieldBinding(pair);
        return pair;
    }
    
    private DataPairFieldInteger createDataPairFieldInteger(BindIntegerInterface p_field, boolean editable) {
        DataPairFieldInteger pair = new DataPairFieldInteger(this,p_field, editable);
        addFieldBinding(pair);
        return pair;
    }
    
    private DataPairFieldBigDecimal createDataPairFieldBigDecimal(BindBigDecimalInterface p_field, boolean editable) {
        DataPairFieldBigDecimal pair = new DataPairFieldBigDecimal(this,p_field, editable);
        addFieldBinding(pair);
        return pair;
    }
    
    private DataPairFieldDouble createDataPairFieldDouble(BindDoubleInterface p_field, boolean editable) {
        DataPairFieldDouble pair = new DataPairFieldDouble(this,p_field, editable);
        addFieldBinding(pair);
        return pair;
    }
    
    private DataPairFieldCurrency createDataPairFieldCurrency(BindCurrencyInterface p_field, boolean editable) {
        DataPairFieldCurrency pair = new DataPairFieldCurrency(this,p_field, editable);
        addFieldBinding(pair);
        return pair;
    }
    
    private DataPairFieldPercentage createDataPairFieldPercentage(BindBigDecimalInterface p_field, boolean editable) {
        DataPairFieldPercentage pair = new DataPairFieldPercentage(this,p_field, editable);
        addFieldBinding(pair);
        return pair;
    }
    
    private DataPairFieldDate createDataPairFieldDate(BindDateInterface p_field, boolean editable) {
        DataPairFieldDate pair = new DataPairFieldDate(this,p_field, editable);
        addFieldBinding(pair);
        return pair;
    }
    
    private DataPairFieldTime createDataPairFieldTime(BindTimeInterface p_field, boolean editable) {
        DataPairFieldTime pair = new DataPairFieldTime(this,p_field, editable);
        addFieldBinding(pair);
        return pair;
    }
    
    private DataPairFieldDateTime createDataPairFieldDateTime(BindDateTimeInterface p_field, boolean editable) {
        DataPairFieldDateTime pair = new DataPairFieldDateTime(this,p_field, editable);
        addFieldBinding(pair);
        return pair;
    }
    
    private DataPairFieldBoolean createDataPairFieldBoolean(BindBooleanInterface p_field, boolean editable) {
        DataPairFieldBoolean pair = new DataPairFieldBoolean(this,p_field, editable);
        addFieldBinding(pair);
        return pair;
    }
    
    private DataPairFieldColor createDataPairFieldColor(BindColorInterface p_field, boolean editable) {
        DataPairFieldColor pair = new DataPairFieldColor(this,p_field, editable);
        addFieldBinding(pair);
        return pair;
    }
        /*
        public DataPairFieldJoin createDataPairFieldJoin(String name, Bindable p_source, JoinPanelJoiner p_joiner, JoinPanelFieldBinder[] p_binders)
        throws BindException {
        DataPairFieldJoin pair = new DataPairFieldJoin(name, this, p_source, p_joiner, p_binders);
        addFieldBinding(pair);
        return pair;
}*/
    
    public DataGroup createDataGroup(FieldGroup group)
    throws BindException {
        if (group.isReadOnly()) {
            return createDataGroup(group,false);
        } else {
            return createDataGroup(group,group.getEntity().mayEdit());
        }
    }
    
    public DataGroup createDataGroup(FieldGroup group,boolean editable)
    throws BindException{
        DataGroup d;
        try {
            if (group.getIconResource()==null){
                d=new DataGroup(group.getName());
            }else{
                d=new DataGroup(group.getName(),group.getIconResource());
            }
            VectorField v=group.getFields();
            for (int t=0;t<v.size();t++){
            	if (!v.get(t).is(Field.FIELD_INVISABLE)){
            		d.add(this.createDataPair(v.get(t),editable));
            	}
            }
            d.addTail();
            return d;
        } catch (IOException ioe){
            throw new BindException(ioe);
        }
    }
    
    public DataGroup createDataGroup(FieldReportGroup group)
    throws BindException{
        DataGroup d;
        try {
            if (group.getIconResource()==null){
                d=new DataGroup(group.getName());
            }else{
                d=new DataGroup(group.getName(),group.getIconResource());
            }
            VectorFieldReport v=group.getFieldReports();
            for (int t=0;t<v.size();t++){
            	//TODO: Temp reports fix. DO NOT DELETE - MD
             //   d.add(v.get(t).createDataGroup(this));
            }
            return d;
        } catch (IOException ioe){
            throw new BindException(ioe);
        }
    }
    
    public DataGroup[] createDataGroup(Entity entity,boolean editable)
    throws BindException {
        DataGroup[] groups = new DataGroup[entity.getFieldGroups().size()+entity.getFieldReportGroups().size()];
        for (int i =0;i < entity.getFieldGroups().size();i++) {
            groups[i] = createDataGroup(entity.getFieldGroups().get(i),editable);
        }
        for (int i =0;i < entity.getFieldReportGroups().size();i++) {
            groups[i+entity.getFieldGroups().size()] = createDataGroup(entity.getFieldReportGroups().get(i));
        }
        return groups;
    }
    
    public DataGroup[] createDataGroup(Entity entity)
    throws BindException {
        return createDataGroup(entity,true);
    }
    
    public Object clone() {
        Binder binder = new Binder();
        binder.binders = binders;
        binder.bindables = bindables;
        binder.deletes = deletes;
        binder.bindings = bindings;
        binder.listners = listners;
        return binder;
    }
    
}

